package com.DB;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;

import Fenetre.FenetrePrincipale;
import Fenetre.ProgressPanel;

import com.*;

/**
 * TableGenerator créé les tables requises pour le comptage d'occurences dans la bdd
 * @author florent
 */
public class TableGenerator {

	private DBConnector db = null; //le connecteur à la bdd
	private HashMap<String,Integer> constante_id = new HashMap<String,Integer>(); //association entre le nom des constantes et leur id dans la bdd

	private FenetrePrincipale fenetre = null;
	
	/**
	 * Construit un TableGenerator
	 * @param db connecteur à la bdd
	 */
	public TableGenerator(DBConnector db){
		this.db = db;
	}

	public TableGenerator(DBConnector db,FenetrePrincipale fenetre){
		this.db = db;
		this.fenetre = fenetre;
	}
	
	/**
	 * Créé une table à partir d'un Type.
	 * Le nom de la table sera "type_<nom du type>"
	 * <br/>
	 * 
	 * CREATE TABLE IF NOT EXISTS type_<nom du type>(
	 * id INTEGER NOT NULL AUTO_INCREMENT 
	 * ,constant_name VARCHAR(400) 
	 * ,constraint PK_<nom de la table> PRIMARY KEY(id) ;
	 * )
	 * 
	 * @param type Type à rentrer dans la bdd
	 */
	public void createTable(Type type,Connection c){
		StringBuffer sb_create = new StringBuffer();

		String table_name = "type_"+type.getName(); //nom de la table

		sb_create.append("CREATE TABLE IF NOT EXISTS ").append(table_name).append("\n");
		sb_create.append("(");

		sb_create.append("id INTEGER NOT NULL").append("\n");
		sb_create.append(",constant_name VARCHAR(400)").append("\n");
		sb_create.append(",constraint PK_").append(table_name).append(" PRIMARY KEY(id)").append("\n");
		sb_create.append(");").append("\n\n");		

		db.executeUpdate(sb_create.toString(),c); //éxécute la requete de création
	}

	public void createTableTypeThrerad(ArrayList<Type> types, int debut, int fin){
		Connection c = db.newConnection();
		for(int i=debut;i<=fin;++i){
			createTable(types.get(i),c);
		}
		db.close(c);
	}
	
	public void createTablesTypes(final ArrayList<Type> types, final int nbThread){

		if(fenetre == null)
			System.out.print("Creation des "+types.size()+" Types : ");

		Thread[] threads = new Thread[nbThread];

		final int taillePartie = types.size()/nbThread;

		for(int i=0;i<nbThread;++i){
			final int partie = i;
			Thread th =new Thread(
					new Runnable(){
						public void run(){
							if(partie == nbThread-1)
								createTableTypeThrerad(types,taillePartie*partie, types.size()-1);
							else
								createTableTypeThrerad(types,taillePartie*partie, taillePartie*(partie+1)-1);
						}
					});
			th.start();
			threads[i] = th;
		}

		for(int i=0;i< threads.length;++i){
			try {
				threads[i].join();
				if(fenetre != null)
					fenetre.setProgressValue(ProgressPanel.CREATION_TYPE,(i*100)/threads.length);
				else
					System.out.print(" "+((i*100)/threads.length)+"% ");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		if(fenetre != null)
			fenetre.setProgressValue(ProgressPanel.CREATION_TYPE,100);
		else
			System.out.println(" ok ");
	}

	/**
	 * Créé une table à partir d'un modèle / prédicat : <br/>
	 * 
	 * CREATE TABLE modele_<nom du type>(
	 * id INTEGER NOT NULL AUTO_INCREMENT 
	 * ,constant_name VARCHAR(30) 
	 * ,truth BOOLEAN 
	 * <br/>
	 * < pour chaque argument :
	 *  	arg<numero> INTEGER 
	 * >
	 * <br/>
	 * < pour chaque argument :
	 * 		,constraint FK_<modele>_arg<type>_<numero> foreign key (arg<numero>) references type_<type>(id)
	 * >
	 * <br/>
	 * ,constraint PK_<nom de la table> PRIMARY KEY(id) ;
	 * )
	 * 
	 * @param le modèle à créer
	 */
	public void createTable(Modele modele,Connection c){
		StringBuffer sb_create = new StringBuffer();

		String table_name = "modele_"+modele.getPredicat();

		sb_create.append("CREATE TABLE IF NOT EXISTS ").append(table_name).append("\n");
		sb_create.append("(").append("\n");

		sb_create.append("id INTEGER NOT NULL").append("\n");
		sb_create.append(",truth BOOLEAN").append("\n");

		StringBuffer foreign = new StringBuffer();

		int arg = 0; //numero de l'argument

		for(Type t : modele.getArguments()){ //ajoute chaque argument
			sb_create.append(",arg").append(arg).append(" INTEGER").append("\n");

			foreign.append(",constraint FK_").append(modele.getPredicat()).append("_").append(t.getName()).append("_").append(arg)
			.append(" foreign key(arg").append(arg).append(")")
			.append(" references type_").append(t.getName())
			.append("(id)\n");

			++arg;
		}

		sb_create.append(",constraint PK_").append(table_name).append(" primary key (id)\n");

		sb_create.append(foreign.toString()); //place les foreign key a la fin de la  String

		sb_create.append(");\n\n");

		db.executeUpdate(sb_create.toString(),c); //éxécute la requete de création
	}

	public void createTablesThread(ArrayList<Modele> modeles, int debut, int fin){
		//	System.out.println(debut+" "+fin);
		Connection c = db.newConnection();
		for(int i=debut;i<=fin;++i){
			createTable(modeles.get(i),c);
		}
		db.close(c);
	}

	public void createTablesModeles(final ArrayList<Modele> modeles,final int nbThread){

		if(fenetre == null)
			System.out.print("Creation des "+modeles.size()+" Modeles : ");
		
		Thread[] threads = new Thread[nbThread];

		final int taillePartie = modeles.size()/nbThread;

		for(int i=0;i<nbThread;++i){
			final int partie = i;
			Thread th =new Thread(
					new Runnable(){
						public void run(){
							if(partie == nbThread-1)
								createTablesThread(modeles,taillePartie*partie, modeles.size()-1);
							else
								createTablesThread(modeles,taillePartie*partie, taillePartie*(partie+1)-1);
						}
					});
			th.start();
			threads[i] = th;
		}

		for(int i=0;i< threads.length;++i){
			try {
				threads[i].join();
				if(fenetre != null)
					fenetre.setProgressValue(ProgressPanel.CREATION_MODELE,(i*100)/threads.length);
				else
					System.out.print(" "+((i*100)/threads.length)+"% ");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		if(fenetre != null)
			fenetre.setProgressValue(ProgressPanel.CREATION_MODELE,100);
		else
			System.out.println(" ok ");

	}

	public void insererConstantesThread(ArrayList<Constante> constantes,int debut, int fin){
		Connection c = db.newConnection();
		System.out.print(".");
		System.out.flush();
		for(int i=debut;i<=fin;i++){
			insert(constantes.get(i),i,c);
		}
		System.out.print("*");
		System.out.flush();
		db.close(c);
	}

	//insere les constantes en threadant
	public void insererConstantes(final ArrayList<Constante> constantes,final int nbThread){

		if(fenetre == null)
			System.out.print("Insertion des "+constantes.size()+" constantes : ");
		
		try {
			BufferedWriter br = new BufferedWriter(new FileWriter("sortie_t.txt"));
			for(int i=0;i<constantes.size();++i){
				br.write(i+"   "+constantes.get(i)+"\n");
			}
			br.close();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		Thread[] threads = new Thread[nbThread];

		final int taillePartie = constantes.size()/nbThread;

		for(int i=0;i<nbThread;++i){
			final int partie = i;
			Thread th =new Thread(
					new Runnable(){
						public void run(){
							if(partie == nbThread-1)
								insererConstantesThread(constantes,taillePartie*partie, constantes.size()-1);
							else
								insererConstantesThread(constantes,taillePartie*partie, taillePartie*(partie+1)-1);
						}
					});
			th.start();
			threads[i] = th;
		}

		for(int i=0;i< threads.length;++i){
			try {
				threads[i].join();
				if(fenetre != null)
					fenetre.setProgressValue(ProgressPanel.INSERTION_CONSTANTES,(i*100)/threads.length);
			//	else
					System.out.print(" "+((i*100)/threads.length)+"% ");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		if(fenetre != null)
			fenetre.setProgressValue(ProgressPanel.INSERTION_CONSTANTES,100);
		else
			System.out.println(" ok ");

	}

	/**
	 * Insert une constante dans la bdd (dans une table type_<nom type>)
	 * 
	 * INSERT INTO type_<nom type> VALUE(
	 * <id generé>,<nom de la constante>
	 * );
	 * 
	 * @param constante constante à inserer
	 */
	public void insert(Constante constante,int id,Connection c){
		
		StringBuffer sb_insert = new StringBuffer();
		sb_insert.append("INSERT INTO ").append("type_").append(constante.getType()).append(" VALUE(");

		constante_id.put(constante.getName(), id);

		sb_insert.append(id).append(",");
		sb_insert.append("\'").append(constante.getName()).append("\'");
		sb_insert.append(");").append("\n");

		db.executeUpdate(sb_insert.toString(),c);
	}


	/**
	 * Insert un atome dans la bdd (dans une table modele_<nom modele>
	 * 
	 * INSERT INTO modele<nom modele> VALUE( <id genéré>, <predicat>, <!negative>
	 *  < pour chaque argument :
	 *  	<id de l'argument>
	 *  >
	 *  );
	 * 
	 * @param atome
	 */
	public void insert(Atom atome,int id, Connection c){
		
		StringBuffer sb_insert = new StringBuffer();

		sb_insert.append("INSERT INTO ").append("modele_").append(atome.getModele().getPredicat())
		.append(" VALUE(");

		sb_insert.append(id).append(",");
		
		if(atome.isNegative())
			sb_insert.append("0").append(",");
		else
			sb_insert.append("1").append(",");
		
		for(int i = 0;i<atome.getConstantes().length; ++i){
			sb_insert.append(constante_id.get(atome.getConstantes()[i].getName())).append(",");
		}
		sb_insert.deleteCharAt(sb_insert.length()-1); //supprime le dernier ","

		sb_insert.append(");").append("\n");

		db.executeUpdate(sb_insert.toString(),c);
	}

	public void insererAtomesThread(ArrayList<Atom> atomes, int debut, int fin){
		Connection c = db.newConnection();
		System.out.print("");
		for(int i=debut;i<=fin;i++){
			insert(atomes.get(i),i,c);
		}
		db.close(c);
	}

	public void insererAtomes(final ArrayList<Atom> atomes, final int nbThread){

		if(fenetre == null)
			System.out.print("Insertion des "+atomes.size()+" Atomes : ");
		
		Thread[] threads = new Thread[nbThread];

		final int taillePartie = atomes.size()/nbThread;


		for(int i=0;i<nbThread;++i){
			final int partie = i;
			Thread th =new Thread(
					new Runnable(){
						public void run(){
							if(partie == nbThread-1)
								insererAtomesThread(atomes,taillePartie*partie, atomes.size()-1);
							else
								insererAtomesThread(atomes,taillePartie*partie, taillePartie*(partie+1)-1);
						}
					});
			th.start();
			threads[i] = th;
		}

		for(int i=0;i< threads.length;++i){
			try {
				threads[i].join();
				if(fenetre != null)
					fenetre.setProgressValue(ProgressPanel.INSERTION_ATOMES,(i*100)/threads.length);
				else
					System.out.print(" "+((i*100)/threads.length)+"% ");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		if(fenetre != null)
			fenetre.setProgressValue(ProgressPanel.INSERTION_ATOMES,100);
		else
			System.out.println(" ok ");
	}

}
